package com.utils;

import org.apache.commons.lang.StringUtils;

import java.util.Arrays;

public class TetrisGlass implements Glass {
    public static final int BITS_PER_POINT = 3;
    private int width;
    private int height;
    private long occupied[];
    private Figure currentFigure;
    private int currentX;
    private int currentY;

    public TetrisGlass(String glassString, int width, int height) {
        this.width = width;
        this.height = height;
        fillGlass(glassString);
    }

    private void fillGlass(String glassString) {
        occupied = new long[height];
        for (int i = 0; i < occupied.length; i++) {
            occupied[i] = 0;
            for (int j = 0; j < width; j++) {
                int plot = (glassString.charAt(i * width + j) == '*') ? 0b111 : 0b0;
                occupied[i] = occupied[i] + plot;
                if (j != width - 1) {
                    occupied[i] = occupied[i] << BITS_PER_POINT;
                }
            }
        }
    }

    public String toString() {
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < occupied.length; i++) {
            result.append(wholeLineAsString(i));
        }
        return result.toString();
    }

    public boolean accept(Figure figure, int x, int y) {
        if (isOutside(figure, x, y)) {
            return false;
        }

        long[] alignedRows = alignFigureRowCoordinatesWithGlass(figure, x, true);
        boolean isOccupied = false;
        for (int i = 0; i < alignedRows.length; i++) {
            long alignedRow = alignedRows[i];
            int rowPosition = y - i + figure.getTop();
            if (rowPosition >= height) {
                continue;
            }
            isOccupied |= (occupied[rowPosition] & alignedRow) > 0;
        }
        return !isOccupied;
    }

    private boolean isOutside(Figure figure, int x, int y) {
        if (isOutsideLeft(figure, x)) {
            return true;
        }
        if (isOutsideRight(figure, x)) {
            return true;
        }
        if (isOutsideBottom(figure, y)) {
            return true;
        }
        return false;
    }

    private boolean isOutsideBottom(Figure figure, int y) {
        return y - figure.getBottom() < 0;
    }

    private boolean isOutsideLeft(Figure figure, int x) {
        return x - figure.getLeft() < 0;
    }

    private boolean isOutsideRight(Figure figure, int x) {
        return x + figure.getRight() >= width;
    }

    public void drop(Figure figure, int x, int y) {
        if (isOutside(figure, x, y)) {
            return;
        }
        int availablePosition = findAvailableYPosition(figure, x, y);
        if (availablePosition >= height) {
            return;
        }
        performDrop(figure, x, availablePosition - figure.getBottom());
//        removeLines();
    }

    private void performDrop(Figure figure, int x, int position) {
        long[] alignedRows = alignFigureRowCoordinatesWithGlass(figure, x, false);
        for (int i = 0; i < alignedRows.length; i++) {
            int rowPosition = position + alignedRows.length - i - 1;
            if (rowPosition >= occupied.length) {
                continue;
            }
            occupied[rowPosition] |= alignedRows[i];
        }
    }

    private void removeLines() {
        for (int i = 0; i < occupied.length; i++) {
            while (wholeLine(i)) {
                System.arraycopy(occupied, i + 1, occupied, i, occupied.length - i - 1);
                occupied[occupied.length - 1] = 0;
            }
        }
    }

    private boolean wholeLine(int rowNum) {
        return wholeLineAsString(rowNum).equals(StringUtils.repeat("*", 10));
    }

    private String wholeLineAsString(int rowNum) {
        String result = "";
        for (int i = 0; i < width; i++) {
            int bitsShift = i * BITS_PER_POINT;
            long figureCode = (occupied[rowNum] & (0b111 << bitsShift)) >> bitsShift;

            if (figureCode == 0) {
                result = " " + result;
            } else if (figureCode == 0b001) {
                result = "o" + result;
            } else {
                result = "*" + result;
            }
        }
        return result;
    }

    private int findAvailableYPosition(Figure figure, int x, int y) {
        int myPosition = y;
        while (accept(figure, x, --myPosition)) {
        }
        myPosition++;
        return myPosition;
    }

    private long[] alignFigureRowCoordinatesWithGlass(Figure figure, int x, boolean ignoreColors) {
        int[] rows = figure.getRowCodes(ignoreColors);
        long[] result = new long[figure.getRowCodes(false).length];
        for (int i = 0; i < rows.length; i++) {
            result[i] = ((long) rows[i]) << ((width - x - figure.getRight() - 1) * BITS_PER_POINT);
        }
        return result;
    }

    public void empty() {
        Arrays.fill(occupied, 0);
    }

    @Override
    public void figureAt(Figure figure, int x, int y) {
        currentFigure = figure;
        this.currentX = x;
        this.currentY = y;
    }

    public boolean isEmpty() {
        for (long anOccupied : occupied) {
            if (anOccupied != 0) {
                return false;
            }
        }
        return true;
    }

	public EmptyCells getOneEmptyCell() {
		System.out.println(occupied[0]);
		if (occupied[0] == 134217727L) {
			return new EmptyCells(0, 1);
		} else {
			return null;
		}
		
	}
}
